<?php

namespace App\Services\EasySms;

use App\Exceptions\ApiErrCode;
use App\Exceptions\ApiException;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Support\Str;
use Overtrue\EasySms\EasySms;
use Overtrue\EasySms\Exceptions\GatewayErrorException;
use Overtrue\EasySms\Exceptions\InvalidArgumentException;
use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;

/**
 * @link https://github.com/overtrue/easy-sms
 * Class EasySmsService
 * @package App\Services\EasySms
 */
class EasySmsService
{

    // 中国的国家区号
    const COUNTRY_CODE_CN = '86';

    const PKX_ALI_SMS_PROXY_URL = 'pkx.ali.sms.proxy_url';

    private static $sms_sign;

    public static function config()
    {
        $config = [
            // HTTP 请求的超时时间（秒）
            'timeout'  => 5.0,

            // 默认发送配置
            'default'  => [
                // 网关调用策略，默认：顺序调用
                'strategy' => \Overtrue\EasySms\Strategies\OrderStrategy::class,

                // 默认可用的发送网关
                'gateways' => [
                    'aliyun',
                ],
            ],
            // 可用的网关配置
            'gateways' => [
                'errorlog' => [
                    'file' => storage_path('logs/easy-sms.log'),
                ],
                'aliyun'   => [
                    'access_key_id'     => config('pkx.ali.sms.access_key_id'),
                    'access_key_secret' => config('pkx.ali.sms.access_key_secret'),
                    'sign_name'         => self::$sms_sign,
                ],
            ],
        ];
        return $config;
    }

    /**
     * @param $phone
     * @param $verify_code
     * @param $dial_code
     *
     * @return array
     * @throws \Exception
     */
    public function registerSend($phone, $verify_code, $dial_code)
    {
        try {
            $send = $this->sendLocaleRegisterMessage($phone, $verify_code, $dial_code);
        } catch (NoGatewayAvailableException  $gateway_exception) {
            /** @var NoGatewayAvailableException|GatewayErrorException|ClientException $exception */
            $exception = $gateway_exception->getException('aliyun');

            if ($exception instanceof ClientException) {
                $message = $exception->getResponse()->getBody();
            } else if ($exception instanceof GatewayErrorException) {
                $message = $exception->getMessage();
            } else {
                $message = (string) $exception;
            }

            throw new ApiException(ApiErrCode::SMS_SEND_FAILED, trans('service.sms_failed_to_send', [
                'tip' => $message,
            ]));
        }

        return $send;
    }

    /**
     * @param $phone
     * @param $verify_code
     *
     * @param $dial_code
     *
     * @return array
     * @throws \Exception
     */
    public function sendLocaleRegisterMessage(string $phone,
                                              string $verify_code,
                                              string $dial_code): array
    {
        $message = [
            'template' => $this->getSmsConfig($dial_code, 'pkx.ali.sms.register_verify_tpl_code'),
            'data'     => [
                // 这里的变量名称为阿里云上定义的变量
                'code' => $verify_code,
            ],
        ];


        $send = $this->sendByProxy(
            $dial_code,
            $this->getSmsConfig($dial_code, 'pkx.ali.sms.register_sign'),
            $this->assembleDialPhone($dial_code, $phone),
            $message
        );

        return $send;
    }

    private function getSmsConfig(string $dial_code, string $key): ?string
    {
        if ($this->shouldUseProxy($dial_code)) {
            return config($key . '_proxy');
        } else {
            return config($key);
        }
    }

    /**
     * aliyun 禁止国外 IP 发送国内短信，需要代理给国内服务器发
     *
     * @param string $sign
     * @param string $to
     * @param array $message
     *
     * @return mixed
     * @throws ApiException
     * @throws \App\Exceptions\ApiSilentException
     */
    public function sendByProxyServer(string $sign, string $to, array $message): array
    {
        $api = $this->smsProxyUrl();

//        dd($api);

        $query = http_build_query([
            'sign'    => $sign,
            'to'      => $to,
            'message' => json_encode($message),
        ]);
        $filename = "$api?$query";
        $res = file_get_contents($filename);

        $res_json = json_decode($res, true);
        if (json_last_error()) {
            $msg = json_last_error_msg();
            throw new ApiException(ApiErrCode::SMS_PROXY_SEND_FAILED, "Proxy Server Error[$msg]");
        }

        if (! empty($res_json['code'])) {
            throw new ApiException(
                ApiErrCode::SMS_PROXY_SEND_FAILED, $res_json['error'] ?? 'Proxy SMS Unknown Error');
        }

        return $res_json;
    }

    /**
     * 代理发送短信
     *
     * @param string $dial_code
     * @param string $sign
     * @param string $to
     * @param array $message
     *
     * @return array
     * @throws \Exception
     */
    public function sendByProxy(string $dial_code, string $sign, string $to, array $message)
    {
        # 阿里云无需设置国外代理了 张晓民 2019-12-06
//        dd(json_encode(['dial_code'=>$dial_code, 'sign'=>$sign, 'to'=>$to, 'array'=>$message]));
//        if ($this->shouldUseProxy($dial_code)) {
//            return $this->sendByProxyServer($sign, $to, $message);
//        } else {
            return $this->send($sign, $to, $message);
//        }
    }

    /**
     * @param string $dial_code
     *
     * @return bool
     */
    private function shouldUseProxy(string $dial_code): bool
    {
        return $dial_code !== self::COUNTRY_CODE_CN;
    }

    /**
     * @return \Illuminate\Config\Repository|mixed
     */
    private function smsProxyUrl()
    {
        return config(self::PKX_ALI_SMS_PROXY_URL);
    }

    /**
     * 直接发送短信
     *
     * @param string $sign
     * @param string $to
     * @param array $message
     *
     * @return array
     * @throws \Exception
     */
    public function send(string $sign, string $to, array $message)
    {
        self::$sms_sign = $sign;
        return (new EasySms(self::config()))->send($to, $message);
    }

    /**
     * @param $phone
     * @param $verify_code
     * @param $dial_code
     *
     * @return array
     * @throws \Exception
     */
    public function loginSend($phone, $verify_code, $dial_code)
    {
        try {
            $send = $this->sendLocaleLoginMessage($phone, $verify_code, $dial_code);
        } catch (NoGatewayAvailableException  $gateway_exception) {
            /** @var NoGatewayAvailableException|GatewayErrorException|ClientException $exception */
            $exception = $gateway_exception->getException('aliyun');

            if ($exception instanceof ClientException) {
                logger($exception->getResponse()->getBody());
            } else if ($exception instanceof GatewayErrorException) {
                logger($exception->getMessage());
            } else {
                logger($exception);
            }

            throw new ApiException(ApiErrCode::SMS_SEND_FAILED, trans('service.sms_failed_to_send', [
                'tip' => $exception->getMessage(),
            ]));
        }

        return $send;
    }

    /**
     * @param $phone
     * @param $verify_code
     *
     * @param $dial_code
     *
     * @return array
     * @throws \Exception
     */
    public function sendLocaleLoginMessage(string $phone,
                                           string $verify_code,
                                           string $dial_code): array
    {
        $message = [
            'template' => $this->getSmsConfig($dial_code, 'pkx.ali.sms.login_verify_tpl_code'),
            'data'     => [
                // 这里的变量名称为阿里云上定义的变量
                'code' => $verify_code,
            ],
        ];

        $send = $this->sendByProxy(
            $dial_code,
            $this->getSmsConfig($dial_code, 'pkx.ali.sms.register_sign'),
            $this->assembleDialPhone($dial_code, $phone),
            $message
        );

        return $send;
    }

    /**
     * @param string $dial_code
     * @param string $phone
     *
     * @return string
     */
    private function assembleDialPhone(string $dial_code, string $phone): string
    {
        if ($this->shouldUseProxy($dial_code)) {
            return $dial_code . $phone;
        } else {
            return $phone;
        }
    }

}
